<template>
	<div class="pagination-container">
		<el-pagination
				ref="paginationRef"
				:current-page.sync="page"
				:layout="layout"
				:page-size="pageSize_"
				:page-sizes="props.pageSizes"
				:small="props.small"
				:total="total"
				@current-change="handleCurrentChange"
				@size-change="handleSizeChange">
		</el-pagination>
		<el-button v-if="props.showRefresh" :size="props.small ? 'small' : 'default'" icon="el-icon-Refresh" @click="buildData"/>
	</div>
</template>

<script lang="ts" setup>

import request from '@/config/axios'
import {includes, isString, orderBy, toSafeInteger} from 'lodash-es'
import {computed, onMounted, ref} from 'vue'

interface OrderByOption {
	attrNames: string[]
	orders: (boolean | 'asc' | 'desc')[]
}

interface Props {
	/* 合并单元格相关（开始） */
	needMerging?: boolean
	mergedAttrName?: Arrayable<string>
	showMergedIndex?: boolean
	orderBy?: OrderByOption
	/* 合并单元格相关（结束） */
	autoBuild?: boolean
	url: string
	params?: AnyObject
	pageSize?: number
	pageSizes?: number[]
	// 紧凑型
	small?: boolean
	simple?: boolean
	showRefresh?: boolean
}

const props = withDefaults(defineProps<Props>(), {
	needMerging: false,
	mergedAttrName: undefined,
	showMergedIndex: false,
	orderBy: () => {
		return {attrNames: [], orders: []}
	},
	autoBuild: true,
	params: () => {
		return {}
	},
	pageSize: 10,
	pageSizes: () => [5, 10, 25, 50, 100],
	simple: false,
	showRefresh: false
})

const emits = defineEmits<{
	(e: 'update:modelValue', value: AnyObject[]): void
	(e: 'update:loading', value: boolean): void
	(e: 'update:orderIndexArr', value: number[][]): void
	(e: 'refreshDone', value: any): void
}>()

const page = ref(1)
const pageSize_ = ref(props.pageSize)
const total = ref(0)
const orderIndexArr_ = ref<number[][]>([])
const tableData_ = ref<AnyObject[]>([])
const layout = computed(() => {
	return props.simple ? 'total, prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
})

const paginationRef = ref()

/**
 * 无论用户给的排序规则是否合理，此方法将会处理得到正确的排序规则
 * 说明：
 * ① 排序规则必须服从于合并字段，因此，如果需要排序，排序字段中必须包含合并字段，且位于最前。
 * ② 不需要排序或指定的排序规则不合理时，默认按照合并字段进行排序。
 */
const getCorrectOrderBy = () => {
	let _orderBy: OrderByOption = {attrNames: [], orders: []}
	if (props.orderBy.attrNames.length * props.orderBy.orders.length <= 0 || props.orderBy.attrNames.length !== props.orderBy.orders.length) {
		_orderBy = getDefaultOrderBy()
	} else {
		if (isString(props.mergedAttrName)) {
			if (props.orderBy.attrNames[0] === props.mergedAttrName) {
				_orderBy.attrNames = props.orderBy.attrNames
				_orderBy.orders = props.orderBy.orders
			} else {
				_orderBy = getDefaultOrderBy()
			}
		} else if (Array.isArray(props.mergedAttrName)) {
			let flag = true
			if (props.orderBy.attrNames.length < props.mergedAttrName.length) {
				flag = false
			} else {
				const attrNamesToCheck = props.orderBy.attrNames.slice(0, props.mergedAttrName.length)
				for (const attrNameBeingChecked of attrNamesToCheck) {
					if (!includes(props.mergedAttrName, attrNameBeingChecked)) {
						flag = false
						break
					}
				}
			}
			if (flag) {
				_orderBy.attrNames = props.orderBy.attrNames
				_orderBy.orders = props.orderBy.orders
			} else {
				_orderBy = getDefaultOrderBy()
			}
		} else {
			_orderBy = getDefaultOrderBy()
		}
	}
	return _orderBy
}

const prepareForOrderIndexArr = () => {
	const orderObj: AnyObject<number[]> = {}
	if (isString(props.mergedAttrName)) {
		tableData_.value.forEach((element, index) => {
			element.rowIndex = index
			if (orderObj[element[<string>props.mergedAttrName]]) {
				orderObj[element[<string>props.mergedAttrName]].push(index)
			} else {
				orderObj[element[<string>props.mergedAttrName]] = []
				orderObj[element[<string>props.mergedAttrName]].push(index)
			}
		})
	} else if (Array.isArray(props.mergedAttrName)) {
		tableData_.value.forEach((element, index) => {
			element.rowIndex = index
			const mergedValueArray = []
			for (const mergedAttrName of props.mergedAttrName!) {
				mergedValueArray.push(element[mergedAttrName])
			}
			const mergedValue = mergedValueArray.join(',')
			if (orderObj[mergedValue]) {
				orderObj[mergedValue].push(index)
			} else {
				orderObj[mergedValue] = []
				orderObj[mergedValue].push(index)
			}
		})
	}
	return orderObj
}
/**
 * 行序号改为合并后的行序号（合并单元格）
 * @param tableData
 * @param orderIndexArr
 */
const handleMergedRowIndex = (tableData: AnyObject[], orderIndexArr: typeof orderIndexArr_.value) => {
	a:for (const row of tableData) {
		const rowIndex = row.rowIndex
		for (let i = 0; i < orderIndexArr.length; i++) {
			const values = orderIndexArr[i]
			for (const value of values) {
				if (rowIndex === value) {
					row.mergedRowIndex = i
					continue a
				}
			}
		}
	}
}

/**
 * 获取相同编号的数组（核心方法）
 */
const getOrderNumber = () => {
	// 如果有排序的要求，且赋值合理，则按照用户的要求进行排序
	const correctOrderBy = getCorrectOrderBy()
	const attrNames = correctOrderBy.attrNames
	const orders = correctOrderBy.orders
	// 按照 attrNames 排序，将 attrNames 相同的数据连在一起
	tableData_.value = orderBy(tableData_.value, attrNames, orders)
	// 为构建 orderIndexArr 做前置准备
	const orderObj = prepareForOrderIndexArr()
	// 需要合并的行的 index 已经存在了同一个数组里，把这些数组存储到 orderIndexArr
	orderIndexArr_.value = []
	for (const k in orderObj) {
		orderIndexArr_.value.push(orderObj[k])
	}
	if (props.showMergedIndex) {
		handleMergedRowIndex(tableData_.value, orderIndexArr_.value)
	}
}

/**
 * 重新加载数据（从第一页）
 */
const reload = () => {
	return new Promise<void>((resolve, reject) => {
		emits('update:loading', true)
		tableData_.value = []
		emits('update:modelValue', tableData_.value)
		const data = {
			'current': 1,
			'params': props.params,
			'size': pageSize_.value
		}
		request.post({
			url: props.url,
			data
		}).then(result => {
			page.value = result.data.current
			pageSize_.value = result.data.size
			total.value = result.data.total
			tableData_.value = result.data.records
			if (props.needMerging) {
				getOrderNumber()
				emits('update:orderIndexArr', orderIndexArr_.value)
				emits('update:modelValue', tableData_.value)
			} else {
				emits('update:modelValue', tableData_.value)
			}
			emits('refreshDone', result.data)
			emits('update:loading', false)
			resolve()
		}).catch(error => {
			emits('update:loading', false)
			reject(error)
		})
	})
}

/**
 * 重新加载数据（当前页）
 */
const buildData = () => {
	return new Promise<void>((resolve, reject) => {
		emits('update:loading', true)
		tableData_.value = []
		emits('update:modelValue', tableData_.value)
		const data = {
			'current': page.value,
			'params': props.params,
			'size': pageSize_.value
		}
		request.post({
			url: props.url,
			data
		}).then(async result => {
			page.value = result.data.current
			pageSize_.value = result.data.size
			total.value = result.data.total
			tableData_.value = result.data.records
			// 检查当前页是否存在
			const pageCount = total.value % pageSize_.value === 0 ? total.value / pageSize_.value : toSafeInteger(total.value / pageSize_.value) + 1
			if (pageCount > 0 && page.value > pageCount) {
				page.value = pageCount
				result = await request.post({
					url: props.url,
					data: {
						'current': page.value,
						'params': props.params,
						'size': pageSize_.value
					}
				})
				tableData_.value = result.data.records
			}
			if (props.needMerging) {
				getOrderNumber()
				emits('update:orderIndexArr', orderIndexArr_.value)
				emits('update:modelValue', tableData_.value)
			} else {
				emits('update:modelValue', tableData_.value)
			}
			paginationRef.value.internalCurrentPage = page.value
			emits('refreshDone', result.data)
			emits('update:loading', false)
			resolve()
		}).catch(error => {
			emits('update:loading', false)
			reject(error)
		})
	})
}

const handleSizeChange = (val: number) => {
	pageSize_.value = val
	buildData()
}

const handleCurrentChange = (val: number) => {
	page.value = val
	buildData()
}

/**
 * 当用户给的排序规则和合并字段冲突时，处理得到默认的排序规则
 */
const getDefaultOrderBy = () => {
	const _orderBy: OrderByOption = {attrNames: [], orders: []}
	if (isString(props.mergedAttrName)) {
		_orderBy.attrNames = [props.mergedAttrName]
		_orderBy.orders = ['asc']
	} else if (Array.isArray(props.mergedAttrName)) {
		_orderBy.attrNames = props.mergedAttrName
		for (let i = 0; i < props.mergedAttrName.length; i++) {
			_orderBy.orders.push('asc')
		}
	}
	return _orderBy
}

const clearData = () => {
	page.value = 1
	pageSize_.value = props.pageSize
	total.value = 0
	orderIndexArr_.value = []
	tableData_.value = []
}

defineExpose({
	total,
	page,
	pageSize_,
	reload,
	buildData,
	clearData
})

onMounted(() => {
	if (props.autoBuild) {
		reload()
	}
})

</script>

<style lang="scss" scoped>
.pagination-container {
	margin: 10px 0;
	display: flex;
	justify-content: space-between;
	align-items: center
}
</style>
